﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
using Common.Logging;
using System.Globalization;


namespace Util.Props
{
    /// <summary>
    /// A property sheet which defines a collection of properties for a single component
    /// in the system.
    /// </summary>
    public class PropertySheet : Object,ICloneable
    {

        public static String COMP_LOG_LEVEL = "logLevel";

        private Dictionary<String, S4PropWrapper> registeredProperties = new Dictionary<String, S4PropWrapper>();
        private Dictionary<String, Object> propValues = new Dictionary<String, Object>();

        
        /// <summary>
        /// Maps the names of the component properties to their (possibly unresolved) values.
        /// </summary>
        private Dictionary<String, Object> rawProps = new Dictionary<String, Object>();

        private ConfigurationManager cm;
        private IConfigurable owner;
        private Type ownerClass=null;

        private String instanceName;

        public String InstanceName
        {
            get { return instanceName; }
            set { instanceName = value; }
        }

        public PropertySheet(IConfigurable configurable, String name, RawPropertyData rpd, ConfigurationManager ConfigurationManager)
            :this(configurable, name, ConfigurationManager, rpd)
        {
            owner = configurable;
        }

        public PropertySheet(IConfigurable confClass, String name, ConfigurationManager cm, RawPropertyData rpd) 
        {
            ownerClass = confClass.GetType();
            this.cm = cm;
            this.instanceName = name;

            Dictionary<FieldInfo, Attribute> parsedAttribute = parseClass(confClass);
            if (parsedAttribute == null)
            {
                throw new NullReferenceException("null parsed attribute dictionary");
            }
            setConfigurableClass(parsedAttribute, confClass);

            // now apply all xml properties
            Dictionary<String, Object> flatProps = rpd.flatten(cm).properties;
            Dictionary<String, Object> rawProps = new Dictionary<String, Object>(rpd.properties);

            foreach (String propName in rawProps.Keys)
                if (propValues.ContainsKey(propName))
                    propValues[propName] = flatProps[propName];
                else
                    propValues.Add(propName, flatProps[propName]);
        }

        /// <summary>
        /// Registers a new property which type and default value are defined by the given sphincs property.
        /// </summary>
        /// <param name="propName">The name of the property to be registered.</param>
        /// <param name="property">The property annotation masked by a proxy.</param>
        private void registerProperty(String propName, S4PropWrapper property)
        {
            if (property == null || propName == null)
                throw new InternalConfigurationException(instanceName, propName, "property or its value is null");

            if (!registeredProperties.ContainsKey(propName))
                registeredProperties.Add(propName, property);

            if (!propValues.ContainsKey(propName))
            {
                propValues.Add(propName, null);
                rawProps.Add(propName, null);
            }
        }

        /// <summary>
        /// Returns the property names <code>name</code> which is still wrapped into the annotation instance.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public S4PropWrapper getProperty<T>(String name) 
        {
            if (!propValues.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name,
                        "Unknown property '" + name + "' ! Make sure that you've annotated it.");

            if(!registeredProperties.ContainsKey(name) || registeredProperties[name] == null)
            {
                throw new InternalConfigurationException(instanceName, name, "Property is not an annotated property of " + getConfigurableClass());
            }
            S4PropWrapper s4PropWrapper = registeredProperties[name];
        
            /// is this correct?
            if(!(s4PropWrapper.getAnnotation() is T))
                throw new InternalConfigurationException(instanceName, name, "Property annotation " + s4PropWrapper.getAnnotation() + " doesn't match the required type ");

            return s4PropWrapper;
        }

        /// <summary>
        /// Gets the value associated with this name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public String getString(String name)
        {
            S4PropWrapper s4PropWrapper = getProperty <S4String>(name);
            S4String s4String = ((S4String) s4PropWrapper.getAnnotation());

            if (propValues[name] == null) 
            {
                Boolean isDefDefined = s4String.defaultValue != S4String.NOT_DEFINED;

                if (s4String.mandatory) 
                {
                    if (!isDefDefined)
                        throw new InternalConfigurationException(instanceName, name, Strings.MANDATORY_PROPERTY_NOT_SET);
                }
                if (!propValues.ContainsKey(name))
                    propValues.Add(name, isDefDefined ? s4String.defaultValue : null);
                else
                    propValues[name] = isDefDefined ? s4String.defaultValue : null;
            }

            String propValue = flattenProp(name);

            // Check range
            List<String> range = s4String.range.ToList();
            if (range.Count!=0 && !range.Contains(propValue))
                throw new InternalConfigurationException(instanceName, name, " is not in range (" + range + ')');

            return propValue;
        }

        private String flattenProp(String name) 
        {
            Object value = propValues[name];
            return value is String ? (String)value : null;
        }

        /// <summary>
        /// Gets the value associated with this name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public int getInt(String name) 
        {
            S4PropWrapper s4PropWrapper = getProperty<S4Integer>(name);
            S4Integer s4Integer = (S4Integer) s4PropWrapper.getAnnotation();

            if (propValues[name] == null) 
            {
                Boolean isDefDefined = (s4Integer.defaultValue != S4Integer.NOT_DEFINED);

                if (s4Integer.mandatory) 
                {
                    if (!isDefDefined)
                        throw new InternalConfigurationException(instanceName, name, Strings.MANDATORY_PROPERTY_NOT_SET);
                }
                else if (!isDefDefined)
                    throw new InternalConfigurationException(instanceName, name, "no default value for non-mandatory property");

                if (propValues.ContainsKey(name))
                    propValues[name] = s4Integer.defaultValue;
                else
                    propValues.Add(name, s4Integer.defaultValue);
            }

            Object propObject = propValues[name];
            int propValue = propObject is int ? (int) propObject : int.Parse(flattenProp(name));

            int[] range = s4Integer.range;
            if (range.Length != 2)
                throw new InternalConfigurationException(instanceName, name, range.ToString() + " is not of expected range type, which is {minValue, maxValue)");

            if (propValue < range[0] || propValue > range[1])
                throw new InternalConfigurationException(instanceName, name, " is not in range (" + range.ToString() + ')');

            return propValue;
        }

        /// <summary>
        /// Gets the value associated with this name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public float getFloat(String name)
        {
            return (float)getDouble(name);
        }

        /// <summary>
        /// Gets the value associated with this name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public double getDouble(String name)
        {
            S4PropWrapper s4PropWrapper = getProperty<S4Double>(name);
            S4Double s4Double = (S4Double) s4PropWrapper.getAnnotation();

            if (propValues[name] == null) 
            {
                Boolean isDefDefined = (s4Double.defaultValue != S4Double.NOT_DEFINED);

                if (s4Double.mandatory) 
                {
                    if (!isDefDefined)
                        throw new InternalConfigurationException(instanceName, name, "mandatory property is not set!");
                } else 
                    if (!isDefDefined)
                        throw new InternalConfigurationException(instanceName, name, "no default value for non-mandatory property");

                if (!propValues.ContainsKey(name))
                    propValues.Add(name, s4Double.defaultValue);
                else
                    propValues[name] = s4Double.defaultValue;
            }


            Object propObject = propValues[name];
            Double propValue;
	
            if (propObject is Double)
    	        propValue = (Double)propObject;
            else
                if (propObject is Decimal)
                    propValue = Convert.ToDouble(propObject);
                else
                {
                    NumberStyles style;
                    CultureInfo culture;

                    style = NumberStyles.Any;
                    culture = CultureInfo.CreateSpecificCulture("en-GB");

                    if (!Double.TryParse(flattenProp(name),style,culture, out propValue))
                    {
                        throw new FormatException("invalid format for property:" + propObject + "=" + name);
                    }
                    //propValue = Double.Parse(flattenProp(name));
                }

            double[] range = s4Double.range;
            if (range.Length != 2)
                throw new InternalConfigurationException(instanceName, name, range.ToString() + " is not of expected range type, which is {minValue, maxValue)");

            if (propValue < range[0] || propValue > range[1])
                throw new InternalConfigurationException(instanceName, name, " is not in range (" + range.ToString() + ")");

            return propValue;
        }

        /// <summary>
        /// Gets the value associated with this name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Boolean getBoolean(String name)
        {
            S4PropWrapper s4PropWrapper = getProperty<S4Boolean>(name);
            S4Boolean s4Boolean = (S4Boolean) s4PropWrapper.getAnnotation();

            if (propValues.ContainsKey(name))
                propValues[name] = s4Boolean.defaultValue;
            else
                propValues.Add(name, s4Boolean.defaultValue);
 
            Object propObject = propValues[name];
            Boolean propValue;
        
            if (propObject is Boolean)
                propValue = (Boolean) propObject;
            else
                propValue = Boolean.Parse(flattenProp(name));
    
            return propValue;
        }

        /// <summary>
        /// Gets a component associated with the given parameter name. First search
        /// the component in property table, then try to get component by name from
        /// the manager, then creates component with default properties.
        /// </summary>
        /// <param name="name">the parameter name</param>
        /// <returns>the component associated with the name</returns>
        public IConfigurable getComponent(String name)
        {
            // First search the component in property table
            S4PropWrapper s4PropWrapper = getProperty<S4Component>(name);
            IConfigurable configurable = null;

            S4Component s4Component = (S4Component) s4PropWrapper.getAnnotation();
            Type expectedType = s4Component.type;

            Object propVal = propValues[name];

            if (propVal != null && propVal is IConfigurable) 
            {
                return (IConfigurable) propVal;
            }

            //then try to get component by name from the manager
            if (propVal != null && propVal is String) 
            {
                PropertySheet ps = cm.getPropertySheet(flattenProp(name));
                if (ps != null)
                    configurable = ps.getOwner();
                else
                    throw new InternalConfigurationException(instanceName, name, "component '" + flattenProp(name)
                            + "' is missing");
            }

            if (configurable != null && expectedType.IsInterface)
            {
                Type[] allIncludedInterfaces = configurable.GetType().GetInterfaces();
                if (!allIncludedInterfaces.Contains(expectedType))
                    throw new InternalConfigurationException(instanceName, name, "mismatch between annotation and component type");
            }
            else if (configurable != null && expectedType.IsClass)
            {
                if (configurable.GetType()!= expectedType)
                {
                    Type baseType = configurable.GetType().BaseType;
                    Boolean isExpectedType = false;
                    while (baseType != null)
                    {
                        if (baseType == expectedType)
                        {
                            isExpectedType = true;
                            break;
                        }
                        baseType = baseType.BaseType;
                    }

                    if (!isExpectedType)
                        throw new InternalConfigurationException(instanceName, name, "mismatch between annotation and component type");            
                }
                    
            }
            else
                Trace.WriteLine("desired type isn't interface");

            if (configurable != null) 
            {
                if (propValues.ContainsKey(name))
                    propValues[name] = configurable;
                else
                    propValues.Add(name, configurable);
                return configurable;
            }

            configurable = getComponentFromAnnotation(name, s4Component);

            if (propValues.ContainsKey(name))
                propValues[name] = configurable;
            else
                propValues.Add(name, configurable);
            return configurable;
        }

        private IConfigurable getComponentFromAnnotation(String name, S4Component s4Component) 
        {
            IConfigurable configurable;
           
            Type defClass = s4Component.defaultClass;

            if (defClass == null && s4Component.mandatory)
            {
                throw new InternalConfigurationException(instanceName, name, "mandatory property is not set!");
            }

            if (defClass.IsAbstract && s4Component.mandatory)
                throw new InternalConfigurationException(instanceName, name, defClass.Name + " is abstract!");

            // because we're forced to use the default type, make sure that it
            // is set
            if (defClass == typeof(IConfigurable))
            {
                if (s4Component.mandatory)
                {
                    throw new InternalConfigurationException(instanceName, name, InstanceName
                            + ": no default class defined for " + name);
                }
                else
                {
                    return null;
                }
            }

            configurable = ConfigurationManager.getInstance((IConfigurable)Activator.CreateInstance(defClass.GetType()));
            if (configurable == null)
            {
                throw new InternalConfigurationException(instanceName, name, "instantiation of referenenced configurable failed");
            }
        
            return configurable;
        }
                        
        /// <summary>
        /// Returns true if the owner of this property sheet is already instantiated.
        /// </summary>
        /// <returns></returns>
        public Boolean isInstanciated() 
        {
            return !(owner == null);
        }

        /// <summary>
        /// Returns the class of of a registered component property without instantiating it.
        /// </summary>
        /// <param name="propName"></param>
        /// <returns></returns>
        public IConfigurable getComponentClass(String propName) 
        {
            IConfigurable defClass = null;
            throw new NotImplementedException();
            //if (propValues[propName] != null)
            //{
            //    try {
            //        // attention!!!!!
            //        //Class<?> objClass = Class.forName((String) propValues[propName]);
            //        //defClass = objClass.asSubclass(Configurable.class);
            //        throw new NotImplementedException("1");
            //    } 
            //    catch (Exception e) {///ClassNotFoundException
            //        //PropertySheet ps = cm.getPropertySheet(flattenProp(propName));
            //        //defClass = ps.ownerClass.;
            //        throw new NotImplementedException("2");
            //    }
            //}
            //else 
            //{
            //    S4Component comAnno = (S4Component) registeredProperties[propName].getAnnotation();
            //    defClass = (IConfigurable)comAnno.defaultClass;
            //    if (comAnno.mandatory)
            //        defClass = null;
            //}

            return defClass;
        }

        /// <summary>
        /// Gets a list of float numbers associated with the given parameter name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public List<String> getStringList(String name)
        {
            getProperty<S4StringList>(name);
            return ConfigurationManagerUtils.toStringList (propValues[name]);
        }

        /// <summary>
        /// Gets a list of components associated with the given parameter name
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="name">the parameter name</param>
        /// <param name="tclass">the class of the list elements</param>
        /// <returns>the component associated with the name</returns>
        public  List<T> getComponentList<T>(String name)
        {
            getProperty<S4ComponentList>(name);

            List<System.Object> components = null;
            if (propValues[name]!=null)
                components = ((List<String>)(propValues[name])).ToList<Object>(); 



            S4ComponentList annotation = (S4ComponentList) registeredProperties[name].getAnnotation();
            Trace.Assert(annotation is S4ComponentList);
            // no components names are available and no component list was yet
            // loaded therefore load the default list of components from the 
            // annotation
            if (components == null) 
            {
                List<IConfigurable> defaultComponents = new List<IConfigurable>();
                foreach (IConfigurable defClass in annotation.defaultList) 
                {
                    defaultComponents.Add(ConfigurationManager.getInstance(defClass));
                }

                if (propValues.ContainsKey(name))
                    propValues[name] = defaultComponents;
                else
                    propValues.Add(name, defaultComponents);
        
            } 
            else if (components.Count!=0  && !(components[0] is IConfigurable)) 
            {

                List<IConfigurable> resolvedComponents = new List<IConfigurable>();

                foreach (Object componentName in components) 
                {
                    IConfigurable configurable = cm.lookup((String) componentName);

                    if (configurable != null) 
                    {
                        resolvedComponents.Add(configurable);
                    } 
                    else if (!annotation.beTolerant) 
                    {
                        throw new InternalConfigurationException(name,
                                (String) componentName, "lookup of list-element '"+ componentName + "' failed!");
                    }
                }

                if (propValues.ContainsKey(name))
                    propValues[name] = resolvedComponents;
                else
                    propValues.Add(name, resolvedComponents);
            }


            List<T> result = new List<T>();
            List<IConfigurable> values = (List<IConfigurable>)propValues[name];
            try
            {
                foreach (IConfigurable obj in values)
                {
                    result.Add((T)obj);
                }
            }
            catch 
            {
                throw new InternalConfigurationException(instanceName,
                            name, "Not all elements have required type " + typeof(T).Name + " Found one of type ");
            }
            return result;
        }

        /// <summary>
        /// arses the string with multiple URL's separated by ;. Return the list of
        /// resources to load
        /// </summary>
        /// <param name="name">list with URL's</param>
        /// <returns>ist of resources</returns>
        public List<Uri> getResourceList(String name) 
        {
            List<Uri> resourceList = new List<Uri>();
            String pathListString = getString(name);

            if (pathListString != null) 
            {
                foreach (String url in (pathListString.Split(new Char[]{';'}))) 
                {
                    try 
                    {
                        Uri resourceUrl = new Uri(url);
                        resourceList.Add(resourceUrl);
                    } 
                    catch (UriFormatException mue) 
                    {
                        //IllegalArgumentException
                        throw new ArgumentException(url + " is not a valid URL. " + mue.ToString());
                    }
                }
            }
            return resourceList;
        }


        /// <summary>
        /// arses the string with multiple URL's separated by ;. Return the list of
        /// resources to load
        /// </summary>
        /// <param name="name">list with URL's</param>
        /// <returns>ist of resources</returns>
        public List<String> getResourceStringList(String name)
        {
            List<String> resourceList = new List<String>();
            String pathListString = getString(name);

            if (pathListString != null)
            {
                foreach (String url in (pathListString.Split(new Char[] { ';' })))
                {
                    try
                    {
                        resourceList.Add(url);
                    }
                    catch (UriFormatException mue)
                    {
                        //IllegalArgumentException
                        throw new ArgumentException(url + " is not a valid URL. " + mue.ToString());
                    }
                }
            }
            return resourceList;
        }

        /// <summary>
        /// Returns the owner of this property sheet. In most cases this will be the configurable instance which was
        /// instrumented by this property sheet.
        /// </summary>
        /// <returns></returns>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public IConfigurable getOwner() 
        {
            try {

                if (!isInstanciated()) 
                {
                    // ensure that all mandatory properties are set before instantiating the component
                    Collection<String> undefProps = getUndefinedMandatoryProps();
                    if (undefProps.Count!=0) 
                    {
                        StringBuilder sb = new StringBuilder();
                        foreach (String prop in undefProps)
                            sb.Append(prop + ",");
                        throw new InternalConfigurationException(instanceName,
                                sb.ToString(), "[" + instanceName + "]" + Strings.MANDATORY_PROPERTY_NOT_DEFINED + sb.ToString());
                    }
                    owner = (IConfigurable)Activator.CreateInstance(ownerClass);
                    owner.newProperties(this);
                }
            }
            catch (MissingMethodException e)
            {
                throw new InternalConfigurationException(e, instanceName, null, "Can't access class " + ownerClass);
            }
            catch (FormatException e)
            {
                Trace.WriteLine(instanceName + ": invalid format value for property! (" + e.Message + ")");
                throw new InternalConfigurationException(e, instanceName, null, "Can't instantiate class " + ownerClass);
            }
            catch (Exception e) 
            {
                throw new InternalConfigurationException(e, instanceName, null, "Can't instantiate class " + ownerClass);
            }

            return owner;
        }


        /// <summary>
        /// Returns the set of all component properties which were tagged as mandatory but which are not set (or no default
        /// value is given).
        /// </summary>
        /// <returns></returns>
        public Collection<String> getUndefinedMandatoryProps() 
        {
            Collection<String> undefProps = new Collection<String>();
            foreach (String propName in  getRegisteredProperties()) 
            {
                Attribute anno = registeredProperties[propName].getAnnotation();

                Boolean isMandatory = false;
                if (anno is S4Component) 
                {
                    isMandatory = ((S4Component) anno).mandatory && ((S4Component) anno).defaultClass == null;
                } 
                else if (anno is S4String) 
                {
                    isMandatory = ((S4String) anno).mandatory && ((S4String) anno).defaultValue.Equals(S4String.NOT_DEFINED);
                } 
                else if (anno is S4Integer) 
                {
                    isMandatory = ((S4Integer) anno).mandatory && ((S4Integer) anno).defaultValue == S4Integer.NOT_DEFINED;
                } 
                else if (anno is S4Double) 
                {
                    isMandatory = ((S4Double) anno).mandatory && ((S4Double) anno).defaultValue == S4Double.NOT_DEFINED;
                }

                if (isMandatory && !((rawProps.ContainsKey(propName) && rawProps[propName]!=null) || (propValues.ContainsKey(propName) && propValues[propName] != null)))
                    undefProps.Add(propName);
            }
            return undefProps;
        }

        /// <summary>
        /// Returns the names of registered properties of this PropertySheet object. 
        /// </summary>
        /// <returns></returns>
        public List<String> getRegisteredProperties()
        {
           return registeredProperties.Keys.ToList();
        }

        /// <summary>
        /// Returns the class of the owner configurable of this property sheet.
        /// </summary>
        /// <returns></returns>
        public Type getConfigurableClass() 
        {
            return ownerClass;
        }

        /// <summary>
        /// Sets the configurable class of this object.
        /// RuntimeException if the the <code>IConfigurable</code> is already instantiated.
        /// </summary>
        /// <param name="confClass"></param>
        void setConfigurableClass(Dictionary<FieldInfo, Attribute> classProps, IConfigurable confClass) 
        {
            // Don't allow changes of the class if the configurable has already been instantiated
            if (isInstanciated())
                throw new SystemException("class is already instantiated");

            // clean up the properties if necessary
	        registeredProperties.Clear();

            foreach(KeyValuePair<FieldInfo, Attribute> entry in classProps) 
            {
                try 
                {
                    //String propertyName = (String)entry.Key.Name;
                    String propertyName = (String)entry.Key.GetValue(null);
                    // make sure that there is not already another property with this name
                    if (registeredProperties.ContainsKey(propertyName))
                        throw new Exception("duplicate property-name for different properties: " + propertyName + " for the class " + confClass.GetType().ToString());
                    registerProperty(propertyName, new S4PropWrapper(entry.Value));
                } 
                catch (Exception e) 
                {
                    throw e;
                }
            }
        }

        private static Dictionary<FieldInfo, Attribute> parseClass(IConfigurable configurable) 
        {
            List<FieldInfo> classFields = new List<FieldInfo>();
            classFields.AddRange(configurable.GetType().GetFields());

            Type baseType = configurable.GetType().BaseType;
            while (baseType!=null)
            {
                FieldInfo[] classNestedFields = baseType.GetFields();
                if (classNestedFields.Length > 0)
                    classFields.AddRange(classNestedFields);
                baseType = baseType.BaseType;
            }
           

            Dictionary<FieldInfo, Attribute> s4props = new Dictionary<FieldInfo, Attribute>();
            foreach(FieldInfo field in classFields) 
            {
                Object[] annotations = field.GetCustomAttributes(true);

                foreach (Attribute annotation in annotations) 
                {
                    Object[] superAnnotations = annotation.GetType().GetCustomAttributes(true);

                    foreach(Attribute superAnnotation in superAnnotations) 
                    {
                        if (superAnnotation is S4Property) 
                        {
                            ///int fieldModifiers = field;
                            Trace.Assert( field.IsStatic, "property fields are assumed to be static");
                            Trace.Assert( field.IsPublic,  "property fields are assumed to be public");
                            //Trace.Assert( Modifier.isFinal(fieldModifiers) : "property fields are assumed to be final";
                            Trace.Assert( field.FieldType == typeof(String), "properties fields are assumed to be instances of String");

                            s4props.Add(field, annotation);
                        }
                    }
                }
            }

            return s4props;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="propName"></param>
        /// <param name="cmName"></param>
        /// <param name="value"></param>
        private void applyConfigurationChange(String propName, Object cmName, Object value) 
        {
            if (!rawProps.ContainsKey(propName))
                rawProps.Add(propName, cmName);
            else
                rawProps[propName] =  cmName;

            if (!propValues.ContainsKey(propName))
                propValues.Add(propName, value != null ? value : cmName);
            else
                propValues[propName]= value != null ? value : cmName;

            if (!String.IsNullOrEmpty(instanceName))
                cm.fireConfChanged(instanceName, propName);

            if (owner != null)
                owner.newProperties(this);
        }


        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name">the simple property name</param>
        /// <param name="value"></param>
        public void setString(String name, String value)
        {
            // ensure that there is such a property
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered string-property");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4String))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type string");

            applyConfigurationChange(name, value, value);
        }

        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name"></param>
        /// <param name="value"></param>
        public void setInt(String name, int value)
        {
            // ensure that there is such a property
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered int-property");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4Integer))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type int");

            applyConfigurationChange(name, value, value);
        }

        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name">the simple property name</param>
        /// <param name="value">the value for the property</param>
        public void setDouble(String name, double value)
        {
            // ensure that there is such a property
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered double-property");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4Double))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type double");

            applyConfigurationChange(name, value, value);
        }


        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name">the simple property name</param>
        /// <param name="value">the value for the property</param>
        public void setBoolean(String name, Boolean value)
        {
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered Boolean-property");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4Boolean))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type Boolean");

            applyConfigurationChange(name, value, value);
        }

        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name">the simple property name</param>
        /// <param name="value">the value for the property</param>
        public void setComponent(String name, String cmName, IConfigurable value)
        {
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered compontent");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4Component))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type component");


            applyConfigurationChange(name, cmName, value);
        }

        /// <summary>
        /// Sets the given property to the given name
        /// </summary>
        /// <param name="name">the simple property name</param>
        /// <param name="valueNames">the list of names of the configurables within the configuration manager (required for
        ///                   serialization only)</param>
        /// <param name="value">the value for the property</param>
        public void setComponentList(String name, List<String> valueNames, List<IConfigurable> value)
        {
            if (!registeredProperties.ContainsKey(name))
                throw new InternalConfigurationException(instanceName, name, '\'' + name +
                        "' is not a registered component-list");

            Attribute annotation = registeredProperties[name].getAnnotation();
            if (!(annotation is S4ComponentList))
                throw new InternalConfigurationException(instanceName, name, '\'' + name + "' is of type component-list");

            rawProps.Add(name, valueNames);
            propValues.Add(name, value);

            applyConfigurationChange(name, valueNames, value);
        }

        /// <summary>
        /// Sets the raw property to the given name
        /// </summary>
        /// <param name="key">the simple property name</param>
        /// <param name="val">the value for the property</param>
        void setRaw(String key, Object val)
        {
            rawProps.Add(key, val);
            propValues.Add(key, null);
        }

        /// <summary>
        /// Gets the raw value associated with this name
        /// </summary>
        /// <param name="name"> name the name</param>
        /// <returns>the value as an object (it could be a String or a String[] depending upon the property type)</returns>
        public Object getRaw(String name)
        {
            return rawProps[name];
        }

        /// <summary>
        /// Gets the raw value associated with this name, no global symbol replacement is performed.
        /// </summary>
        /// <param name="name">the name</param>
        /// <returns>the value as an object (it could be a String or a String[] depending upon the property type)</returns>
        public Object getRawNoReplacement(String name)
        {
            return rawProps[name];
        }

        /// <summary>
        /// Returns the type of the given property.
        /// </summary>
        /// <param name="propName"></param>
        /// <returns></returns>
        public PropertyType getType(String propName) 
        {
            S4PropWrapper wrapper = registeredProperties[propName];
            if (wrapper == null) {
                throw new InternalConfigurationException(instanceName, propName, " is not a valid property of" + getConfigurableClass());
            }

            Attribute annotation = wrapper.getAnnotation();
            if (annotation is S4Component)
                return PropertyType.COMPONENT;
            else if (annotation is S4ComponentList)
                return PropertyType.COMPONENT_LIST;
            else if (annotation is S4Integer)
                return PropertyType.INT;
            else if (annotation is S4Double)
                return PropertyType.DOUBLE;
            else if (annotation is S4Boolean)
                return PropertyType.BOOLEAN;
            else if (annotation is S4String)
                return PropertyType.STRING;
            else
                throw new SystemException("Unknown property type");
        }

        /// <summary>
        /// Gets the owning property manager
        /// </summary>
        /// <returns></returns>
        public ConfigurationManager getPropertyManager()
        {
            return cm;
        }

        public TraceListener getLogger(int index)
        {
            return  Trace.Listeners[index];
        }

        public void setCM(ConfigurationManager cm)
        {
            this.cm = cm;
        }

        /// <summary>
        /// Returns true if two property sheet define the same object in terms of configuration. The owner (and the parent
        /// configuration manager) are not expected to be the same.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        override public Boolean Equals(Object obj) 
        {
            if (obj == null || !(obj is PropertySheet))
                return false;

            PropertySheet ps = (PropertySheet) obj;
            if (!rawProps.Keys.Equals(ps.rawProps.Keys))
                return false;

            // maybe we could test a little bit more here. suggestions?
            return true;
        }

        public override int GetHashCode()
        {
            Trace.Assert(false, "hashCode not designed");
  	  	    return 1; // any arbitrary constant will do 
        }

        public override string ToString()
        {
            return instanceName + "; isInstantiated=" + isInstanciated() + "; props=" + rawProps.Keys;
        }

        
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Object Clone()
        {
            PropertySheet ps = (PropertySheet)new Object();

            ps.registeredProperties = new Dictionary<String, S4PropWrapper>(this.registeredProperties);
            ps.propValues = new Dictionary<String, Object>(this.propValues);

            ps.rawProps = new Dictionary<String, Object>(this.rawProps);

            // make deep copy of raw-lists
            foreach (String regProp in ps.getRegisteredProperties()) 
            {
                if (getType(regProp) == PropertyType.COMPONENT_LIST) 
                {
                    ps.rawProps.Add(regProp, ConfigurationManagerUtils.toStringList(rawProps[regProp]));
                    ps.propValues.Add(regProp, null);
                }
            }

            ps.cm = cm;
            ps.owner = null;
            ps.InstanceName = this.InstanceName;

            return ps;
        }

        /// <summary>
        /// Validates a configuration, by ensuring that only valid property-names have been used to configure the component.
        /// </summary>
        /// <returns></returns>
        public Boolean Validate() 
        {
            foreach (String propName in rawProps.Keys) 
            {
                if (propName.Equals(ConfigurationManagerUtils.GLOBAL_COMMON_LOGLEVEL))
                    continue;

                if (!registeredProperties.ContainsKey(propName))
                    return false;
            }

            return true;
        }

    }
}
